package com.leon.datalink.resource.driver;

import cn.com.soyuan.core.connection.ConnectionManager;
import cn.com.soyuan.enums.PlcModel;
import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
import com.leon.datalink.core.config.ConfigProperties;
import com.leon.datalink.core.exception.impl.DataValidateException;
import com.leon.datalink.core.utils.Loggers;
import com.leon.datalink.core.utils.StringUtils;
import com.leon.datalink.resource.AbstractDriver;
import com.leon.datalink.resource.constans.DriverModeEnum;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author: zyl
 * @date 2025/2/19 16:53:31
 */
public class S7PLCDriver extends AbstractDriver {

    private S7PLC s7PLC;

    @Override
    public void create(Consumer<Object> consumer) throws Exception {
        if (StringUtils.isEmpty(properties.getString("ip"))) throw new DataValidateException();
        if (StringUtils.isEmpty(properties.getString("port"))) throw new DataValidateException();
        if (StringUtils.isEmpty(properties.getString("model"))) throw new DataValidateException();

        s7PLC = ConnectionManager.initPLCConnection(properties.getString("ip"),
                PlcModel.fromString(properties.getString("model")));
    }

    @Override
    public void destroy() throws Exception {
        s7PLC.close();
    }

    @Override
    public boolean test(ConfigProperties properties) {

        if (StringUtils.isEmpty(properties.getString("ip"))) return false;
        if (StringUtils.isEmpty(properties.getString("port"))) throw new DataValidateException();
        if (StringUtils.isEmpty(properties.getString("model"))) return false;

        try {
            s7PLC = ConnectionManager.initPLCConnection(properties.getString("ip"),
                    PlcModel.fromString(properties.getString("model")),
                    Integer.parseInt(properties.getString("port")));
            boolean b = s7PLC.checkConnected();
            return b;
        } catch (Exception e) {
            Loggers.DRIVER.error("driver test {}", e.getMessage());
            return false;
        }
    }

    @Override
    public void handleData(Object data, Consumer<Object> output) throws Exception {
        if (driverMode.equals(DriverModeEnum.SOURCE)) {
            this.read(data, output);
        } else if (driverMode.equals(DriverModeEnum.TARGET)) {
            this.write(data, output);
        }
    }


    private void write(Object data, Consumer<Object> output) throws Exception {
        String address = properties.getString("address");
        String dataValue = properties.getString("dataValue");
        String dataType = properties.getString("dataType");

        if (StringUtils.isEmpty(address)) throw new DataValidateException();
        if (StringUtils.isEmpty(dataValue)) throw new DataValidateException();
        ensurePLCConnection();

        // 根据不同的数据类型执行不同的写操作
        if ("REAL".equalsIgnoreCase(dataType)) {
            // 写入浮点数（32位）
            s7PLC.writeFloat32(address, Float.parseFloat(dataValue));
        } else if ("LREAL".equalsIgnoreCase(dataType)) {
            // 写入双精度浮点数（64位）
            s7PLC.writeFloat64(address, Double.parseDouble(dataValue));
        } else if ("BOOL".equalsIgnoreCase(dataType)) {
            // 写入布尔值
            s7PLC.writeBoolean(address, Boolean.parseBoolean(dataValue));
        } else if ("WORD/UINT".equalsIgnoreCase(dataType)) {
            // 写入无符号16位整数
            s7PLC.writeUInt16(address, Integer.parseInt(dataValue));
        } else if ("WORD/INT".equalsIgnoreCase(dataType)) {
            // 写入带符号16位整数
            s7PLC.writeInt16(address, Short.parseShort(dataValue));
        } else if ("DWORD/UDINT".equalsIgnoreCase(dataType)) {
            // 写入无符号32位整数
            s7PLC.writeUInt32(address, Long.parseLong(dataValue));
        } else if ("DWORD/DINT".equalsIgnoreCase(dataType)) {
            // 写入带符号32位整数
            s7PLC.writeInt32(address, Integer.parseInt(dataValue));
        } else if ("STRING".equalsIgnoreCase(dataType)) {
            // 写入字符串
            s7PLC.writeString(address, dataValue);
        } else if ("TIME".equalsIgnoreCase(dataType)) {
            // 写入时间
            s7PLC.writeTime(address, Long.parseLong(dataValue));
        }else if ("DATE".equalsIgnoreCase(dataType)) {
            // 写入日期（LocalDate）
            LocalDate date = LocalDate.parse(dataValue, DateTimeFormatter.ISO_LOCAL_DATE);
            s7PLC.writeDate(address, date);
        } else if ("TIMEOFDAY".equalsIgnoreCase(dataType)) {
            // 写入时间点（LocalTime）
            LocalTime timeOfDay = LocalTime.parse(dataValue, DateTimeFormatter.ISO_LOCAL_TIME);
            s7PLC.writeTimeOfDay(address, timeOfDay);
        } else if ("DTL".equalsIgnoreCase(dataType)) {
            // 写入日期时间长整型（LocalDateTime）
            LocalDateTime dateTime = LocalDateTime.parse(dataValue, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
            s7PLC.writeDTL(address, dateTime);
        } else {
            throw new DataValidateException("Unsupported data type for write operation");
        }
    }



    private void ensurePLCConnection() throws Exception {
        if (!s7PLC.checkConnected()) {
            s7PLC.connect();
        }
    }

    private void read(Object data, Consumer<Object> output) throws Exception {
        List<Map<String, Object>> points = properties.getList("points");
        if (null == points || points.isEmpty()) throw new DataValidateException();

        try {
            if (!s7PLC.checkConnected()) {
                s7PLC.connect();
            }
            List<Map<String, Object>> resultList = points.stream()
                    .map(point -> {
                        String tag = (String) point.get("tag");
                        String address = (String) point.get("address");
                        String type = ((String) point.get("type")).trim();
                        String show = ((String) point.get("show")).trim();
                        String value = "";

                        if ("REAL".equalsIgnoreCase(type)) {
                            value = String.valueOf(s7PLC.readFloat32(address));
                        } else if ("LREAL".equalsIgnoreCase(type)) {
                            value = String.valueOf(s7PLC.readFloat64(address));
                        } else if ("BOOL".equalsIgnoreCase(type)) {
                            value = String.valueOf(s7PLC.readBoolean(address));
                        } else if ("WORD/UINT".equalsIgnoreCase(tag)) {
                            value = String.valueOf(s7PLC.readUInt16(address));
                        } else if ("WORD/INT".equalsIgnoreCase(tag)) {
                            value = String.valueOf(s7PLC.readInt16(address));
                        } else if ("DWORD/UDINT".equalsIgnoreCase(tag)) {
                            value = String.valueOf(s7PLC.readUInt32(address));
                        } else if ("DWORD/DINT".equalsIgnoreCase(tag)) {
                            value = String.valueOf(s7PLC.readInt32(address));
                        } else if ("STRING".equalsIgnoreCase(tag)) {
                            value = String.valueOf(s7PLC.readString(address));
                        } else if ("TIME".equalsIgnoreCase(tag)) {
                            value = String.valueOf(s7PLC.readTime(address));
                        } else if ("DATE".equalsIgnoreCase(tag)) {
                            value = String.valueOf(s7PLC.readDate(address));
                        } else if ("TIMEOFDAY".equalsIgnoreCase(tag)) {
                            value = String.valueOf(s7PLC.readTimeOfDay(address));
                        } else if ("DTL".equalsIgnoreCase(tag)) {
                            value = String.valueOf(s7PLC.readDTL(address));
                        }

                        long timestamp = System.currentTimeMillis();
                        Map<String, Object> result = new HashMap<>();
                        result.put("tag", tag);
                        result.put("success", true);
                        result.put("value", value);
                        result.put("show", show);
                        result.put("timestamp", timestamp);

                        return result;
                    }).collect(Collectors.toList());


            String transferType = properties.getString("transferType", "single");
            if ("single".equals(transferType)) {
                resultList.forEach(item -> output.accept(item));
            } else {
                output.accept(resultList);
            }


        } catch (Exception e) {
            Loggers.DRIVER.error("s7plc read error: {}", e.getMessage());
        }
    }


}
